/*
 * @author Stanislovas Mickus
 * @version 1
 * @date 27 October, 2014
 *
 *  Copyright (C) 2013 Stanislovas Mickus
 *
 * Licensed under the GNU General Public License, Version 3 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.gnu.org/copyleft/gpl.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.stasmobstudios.musicplayer.services;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import com.stasmobstudios.musicplayer.R;
import com.stasmobstudios.musicplayer.broadcasts.PlayBackCommandsReceiver;
import com.stasmobstudios.musicplayer.interfaces.PlaybackServiceInterface;
import com.stasmobstudios.musicplayer.interfaces.UIUpdateCallbacks;

import java.io.IOException;

public class PlaybackService extends Service implements PlaybackServiceInterface {
    private String CLASS_NAME;
    private String[] mTrackDataArray = {};
    private int mCurrentTrackNo = -1;
    private UIUpdateCallbacks mUIUpdateCallbacks;
    private MediaPlayer mMediaPlayer;
    private boolean mMediaPlayerIsPrepared = false;
    private PlayBackCommandsReceiver mPlayBackCommandsReceiver;
    private AudioManager mAudioManager;
    private boolean mPausedByTransientLossOfFocus;
    private boolean mAudioLossOfFocus = false;
    private Context mContext;

    public PlaybackService() {
        super();

        mContext = getBaseContext();
        CLASS_NAME = this.getClass().getSimpleName().toUpperCase();
    }

    /**
     **************************   Playback methods   **************************
     */

    /**
     * Media play / pause
     */
    public boolean mediaPlayPause() {
        if (mTrackDataArray == null) return false;

        if (mMediaPlayer.isPlaying()) {
            mMediaPlayer.pause();
            return false;
        } else {
            if (mCurrentTrackNo == -1) {
                try {
                    mCurrentTrackNo = 0;
                    return mediaPlay(mCurrentTrackNo);
                } catch (Exception e) {
                    Log.e(CLASS_NAME, "mediaPlayPause(): FAILED to play first track");
                    return false;
                }
            } else {
                try {
                    mMediaPlayer.start();
                    return  true;
                } catch (Exception e) {
                    Log.e(CLASS_NAME, "mediaPlayPause(): FAILED to play a track");
                    return false;
                }
            } //else
        } //else
    }

    /**
     * Media play previous track
     */
    public boolean mediaPrev() {
        if (mTrackDataArray == null || !mMediaPlayerIsPrepared) return false;

        if (mCurrentTrackNo - 1 >= 0) {
            try {
                mCurrentTrackNo--;
                return mediaPlay(mCurrentTrackNo);
            } catch (Exception e) {
                Log.e(CLASS_NAME, "mediaPrev(): FAILED to play previous track");
                e.printStackTrace();
                return false;
            }
        } else
            return mMediaPlayer.isPlaying();
    }

    /**
     * Media play next track
     */
    public boolean mediaNext() {
        if (mTrackDataArray == null || !mMediaPlayerIsPrepared) return false;

        if (mCurrentTrackNo + 1 < mTrackDataArray.length) {
            try {
                mCurrentTrackNo++;
                return mediaPlay(mCurrentTrackNo);
            } catch (Exception e) {
                Log.e(CLASS_NAME, "mediaNext(): FAILED to play next track");
                e.printStackTrace();
                return false;
            }
        } else {
            if (mMediaPlayer.isPlaying())
                return true;
            else {
                setAudioManagerAbandonFocus(true);
                return false;
            }
        }
    }

    /**
     * Play given media track
     *
     * @param trackID Track ID to play
     */
    public boolean mediaPlay(int trackID) throws IOException {
        if (mTrackDataArray == null) return false;
        mCurrentTrackNo = trackID;

        if ( mTrackDataArray.length > 0 && trackID < mTrackDataArray.length && trackID >= 0 ) {
            //Initialize track player
            mMediaPlayerIsPrepared = false;
            mMediaPlayer.reset();
            Log.e(CLASS_NAME, "mediaSong(): MediaPlayer sessionID: " + mMediaPlayer.getAudioSessionId());

            try {
                mMediaPlayer.setDataSource(mContext, Uri.parse( mTrackDataArray[trackID] ));
                mMediaPlayer.prepareAsync();
                mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mediaPlayer) {
                        mediaPlayer.start();
                        mMediaPlayerIsPrepared = true;
                        mUIUpdateCallbacks.updateCurrentTrackInfo();
                        mUIUpdateCallbacks.updateCurrentTrackSelection(mCurrentTrackNo);
                        mUIUpdateCallbacks.updatePlayingSate(isMediaPlaying());
                    }
                });
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(CLASS_NAME, "mediaSong(): MediaPlayer FAILED a specified track");
                throw e;
            }
        } else
            return false;

    }

    /**
     * Seek into media track
     *
     * @param procent Procent to seek to
     */
    public boolean mediaSeekToProcent(int procent) {
        if (mMediaPlayerIsPrepared) {
            mMediaPlayer.seekTo( (mMediaPlayer.getDuration() * procent) / 100 );
            return true;
        }
        else
            return false;
    }


    /**
     **************************   Playback getters / setters   **************************
     */
    public void setTrackDataArray(String[] trackDataArray) {
        this.mTrackDataArray = trackDataArray;
        mCurrentTrackNo = -1;
    }

    public void setAuxEffectSendLevel(float level) {
        mMediaPlayer.setAuxEffectSendLevel(level);
    }

    public boolean isPrepared() {
        return mMediaPlayerIsPrepared;
    }

    public int getTrackDuration() {
        if (mMediaPlayerIsPrepared)
            return mMediaPlayer.getDuration();
        else if (mTrackDataArray.length > 0) {
            MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
            try {
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[0]));
                return Integer.valueOf(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
            } catch (Exception e) {
                Log.e(CLASS_NAME, "getTrackDuration(): Failed to setDataSource on: " + Uri.parse(mTrackDataArray[0]));
                return 0;
            }
        }
        else
            return 0;
    }

    public int getTrackCurrentTime() {
        if (mMediaPlayerIsPrepared)
            return mMediaPlayer.getCurrentPosition();
        else
            return 0;
    }

    public String getTrackArtist() {
        try {
            if (mMediaPlayerIsPrepared) {
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                if (mCurrentTrackNo == -1)
                    return "***";
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[mCurrentTrackNo]));
                return mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
            } else if (mTrackDataArray.length > 0) {
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[0]));
                return mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
            } else
                return "***";
        } catch (Exception e) {
            Log.e(CLASS_NAME, "getTrackArtist(): Exception retrieving track artist");
            e.printStackTrace();
            return "***";
        }
    }

    public String getTrackAlbum() {
        try {
            if (mMediaPlayerIsPrepared){
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                if (mCurrentTrackNo == -1)
                    return "***";
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[mCurrentTrackNo]));
                return mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
            } else if (mTrackDataArray.length > 0) {
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[0]));
                return mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
            }
            else
                return "***";
        } catch (Exception e) {
            Log.e(CLASS_NAME, "getTrackAlbum(): Exception retrieving track album");
            e.printStackTrace();
            return "***";
        }
    }

    public String getTrackName() {
        try {
            if (mMediaPlayerIsPrepared) {
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                if (mCurrentTrackNo == -1 && mTrackDataArray.length == 0)
                    return "***";
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[mCurrentTrackNo]));
                return mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
            } else if (mTrackDataArray.length > 0) {
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[0]));
                return mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
            } else
                return "***";
        } catch (Exception e) {
            Log.e(CLASS_NAME, "getTrackName(): Exception retrieving track name");
            e.printStackTrace();
            return "***";
        }
    }

    public byte[] getAlbumArtwork() {
        if (mMediaPlayerIsPrepared){
            MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
            if (mCurrentTrackNo == -1) return null;
            try {
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[mCurrentTrackNo]));
            } catch (Exception e) {
                Log.e(CLASS_NAME, "getAlbumArtwork(): Exception retrieving Albumartwork");
                return null;
            }
            return mediaMetadataRetriever.getEmbeddedPicture();
        } else if (mTrackDataArray.length > 0) {
            MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
            try {
                mediaMetadataRetriever.setDataSource(mContext, Uri.parse(mTrackDataArray[0]));
            } catch (Exception e) {
                Log.e(CLASS_NAME, "getAlbumArtwork(): Exception retrieving Albumartwork");
                return null;
            }
            return mediaMetadataRetriever.getEmbeddedPicture();
        } else
            return null;
    }

    public int getAudioSessionID() {
        return mMediaPlayer.getAudioSessionId();
    }

    public boolean isMediaPlaying() {
        return mMediaPlayer.isPlaying();
    }

    public int getCurrentTrackNo() {
        return mCurrentTrackNo;
    }

    public void setCurrentTrackNo(int currentTrackNo) {
        this.mCurrentTrackNo = currentTrackNo;
    }

    /**
     **************************   Service methods   **************************
     */
    public class LocalBinder extends Binder {
        public PlaybackServiceInterface getService() {
            return PlaybackService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private final IBinder mBinder = new LocalBinder();

    @Override
    public boolean onUnbind(Intent intent) {

        return super.onUnbind(intent);
    }

    /**
     **************************   Connected UI methods   **************************
     */

    /**
     * Init control / UI update callbacks
     */
    public void initControlUICallbacks(UIUpdateCallbacks uiUpdateCallbacks) {
        this.mUIUpdateCallbacks = uiUpdateCallbacks;

        this.mContext = getBaseContext();

        // Registers the PlayBackStateReceiver and its intent filters
        if (mPlayBackCommandsReceiver == null) {
            mPlayBackCommandsReceiver = new PlayBackCommandsReceiver(mUIUpdateCallbacks, this);
            IntentFilter statusIntentFilter = new IntentFilter(ConstantsPlayBack.BROADCAST_PLAYBACK_COMMANDS);
            statusIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
            this.registerReceiver(mPlayBackCommandsReceiver, statusIntentFilter);
        }
    }

    /**
     **************************  Audio focus methods and listener   **************************
     */

    private AudioManager.OnAudioFocusChangeListener mAudioFocusListener = new AudioManager.OnAudioFocusChangeListener() {
        public void onAudioFocusChange(int focusChange) {
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_LOSS:
                    mPausedByTransientLossOfFocus = false;
                    mAudioLossOfFocus = true;
                    mUIUpdateCallbacks.updatePlayingSate(mediaPlayPause());
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    if (isMediaPlaying()) {
                        mPausedByTransientLossOfFocus = true;
                        mUIUpdateCallbacks.updatePlayingSate(mediaPlayPause());
                    }
                    break;
                case AudioManager.AUDIOFOCUS_GAIN:
                    if (mPausedByTransientLossOfFocus) {
                        mPausedByTransientLossOfFocus = false;
                        mUIUpdateCallbacks.updatePlayingSate(mediaPlayPause());
                    }
                    mAudioLossOfFocus = false;
                    break;
            }
        }
    };

    /**
     * Abandon or gain audio focus
     *
     * @param getFocus Set to abandon or to gain audio focus
     */
    private void setAudioManagerAbandonFocus(boolean getFocus) {
        if (getFocus) {
            mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
            mAudioLossOfFocus = false;
        }
        else {
            mAudioManager.abandonAudioFocus(mAudioFocusListener);
            mAudioLossOfFocus = true;
        }
    }

    /**
     **************************   Service lifecycle methods   **************************
     */
    @Override
    public void onCreate() {
        super.onCreate();

        mMediaPlayer = new MediaPlayer();
        //Play next track after current track completion
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                mUIUpdateCallbacks.updatePlayingSate(mediaNext());
            }
        });

        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        setAudioManagerAbandonFocus(false);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i( CLASS_NAME, "Received start id " + startId + ": " + intent );
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        // If the PlayBackStateReceiver still exists, unregister it and set it to null
        if (mPlayBackCommandsReceiver != null) {
            this.unregisterReceiver(mPlayBackCommandsReceiver);
            mPlayBackCommandsReceiver = null;
        }
    }
}
